/**
 * 
 */
package infomanager;

import info.Constant;
import info.Room;

import java.util.ArrayList;
import java.util.Arrays;

import constraints.HardConstraint;
import constraints.SoftConstraint;
import evolution.Gene;
import evolution.Generation;
import evolution.Individual;

/**
 * @author vtkhanh
 * 
 */
public class IndividualManager {
    private static enum MutationMode { RANDOM, LINEAR }
    private static MutationMode mChooseTimeslotMode = MutationMode.RANDOM;
    
    public static void evaluate(Individual individual,
	    Generation nextGeneration, boolean isModified) {
	/*boolean isOverResource = false;
	boolean isDuplicate = false;
	int[] usedResource = new int[Constant.TIMESLOT_NUM];
	ArrayList<Gene> genes = individual.getGenes();
	short[][] groupRegister = new short[GroupManager.getGroups().length][Constant.TIMESLOT_NUM];

	Arrays.fill(usedResource, 0);

	// Hard-constraint: Duplicate + OverResource
	for (Gene gene : genes) {
	    // check used resource
	    int resource = gene.getCourse().getGroup().getCount();
	    usedResource[gene.getTimeslot()] += resource;
	    isOverResource = HardConstraint.isOverResourceLimit(genes,
		    gene.getTimeslot(), usedResource[gene.getTimeslot()]);
	    if (isOverResource && isModified) {
		usedResource[gene.getTimeslot()] = Integer.MIN_VALUE;
		modify(individual, nextGeneration, gene.getTimeslot());
	    }

	    // mark to check sequential period for groups
	    groupRegister[gene.getCourse().getGroup().getGroupId()][gene
		    .getTimeslot()] = (short) 1;

	    // check whether one group(one teacher) has more than one class per
	    // timeslot
	    isDuplicate = HardConstraint.isDuplicate(genes, gene);
	    if (isDuplicate && isModified) {
		modify(individual, nextGeneration, gene);
	    }
	}

	// Soft-constraint: check sequential period for groups
	ArrayList<Gene> modifiedGenes = SoftConstraint.checkSequentialPeriods(
		genes, groupRegister);
	if ((modifiedGenes.size() > 0) && isModified) {
	    modify(individual, nextGeneration, modifiedGenes);
	}

	// Soft-constraint: check if more than a period of a subject for one
	// group in one day
	modifiedGenes = SoftConstraint.checkPeriodsPerSubjectInOneDay(genes);
	if ((modifiedGenes.size() > 0) && isModified) {
	    modify(individual, nextGeneration, modifiedGenes);
	}*/
	evaluateUseOfResource(individual, nextGeneration, isModified);
	
	evaluateDuplicate(individual, nextGeneration, isModified);
	
	evaluateSequentialPeriods(individual, nextGeneration, isModified);
	
	evaluateOverloadedPeriods(individual, nextGeneration, isModified);
    }

    /**
     * Hard constraint: check use of resource
     * 
     * @param individual
     * @param nextGeneration
     * @param isModified
     */
    private static void evaluateUseOfResource(Individual individual,
	    Generation nextGeneration, boolean isModified) {
	boolean isOverResource = false;
	int[] usedResource = new int[Constant.TIMESLOT_NUM];
	ArrayList<Gene> genes = individual.getGenes();

	// Hard-constraint: OverResource
	for (Gene gene : genes) {
	    int resource = gene.getCourse().getGroup().getCount();

	    usedResource[gene.getTimeslot()] += resource;
	    isOverResource = HardConstraint.isOverResourceLimit(genes,
		    gene.getTimeslot(), usedResource[gene.getTimeslot()]);
	    if (isOverResource && isModified) {
		usedResource[gene.getTimeslot()] = Integer.MIN_VALUE;
		modify(individual, nextGeneration, gene.getTimeslot());
	    }
	}
    }     

    /**
     * Hard constraint: check whether one group(one teacher) has more than one 
     * class per timeslot
     * 
     * @param individual
     * @param nextGeneration
     * @param isModified
     */
    private static void evaluateDuplicate(Individual individual,
	    Generation nextGeneration, boolean isModified) {
	ArrayList<Gene> genes = individual.getGenes();
	for (Gene gene : genes) {	   
	    boolean isDuplicate = HardConstraint.isDuplicate(genes, gene);
	    if (isDuplicate && isModified) {
		modify(individual, nextGeneration, gene);
	    }
	}
    }
    
    /**
     * Soft-constraint: check sequential period for groups
     * 
     * @param individual
     * @param nextGeneration
     * @param isModified
     */    
    private static void evaluateSequentialPeriods(Individual individual,
	    Generation nextGeneration, boolean isModified) {
	ArrayList<Gene> genes = individual.getGenes();
	short[][] register = new short[GroupManager.getGroups().length][Constant.TIMESLOT_NUM];
	
	for (Gene gene : genes) {
	    register[gene.getCourse().getGroup().getGroupId()][gene.getTimeslot()] = (short) 1;
	}
	
	ArrayList<Gene> modifiedGenes = SoftConstraint.checkSequentialPeriods(genes, register);
	if ((modifiedGenes.size() > 0) && isModified) {
	    modify(individual, nextGeneration, modifiedGenes);
	}
    }
    
    /**
     * Soft-constraint: check if more than a period of a subject for one group 
     * in one day
     * 
     * @param individual
     * @param nextGeneration
     * @param isModified
     */
    private static void evaluateOverloadedPeriods(Individual individual,
	    Generation nextGeneration, boolean isModified) {
	ArrayList<Gene> genes = individual.getGenes();	
	ArrayList<Gene> modifiedGenes = SoftConstraint.checkPeriodsPerSubjectInOneDay(genes);
	if ((modifiedGenes.size() > 0) && isModified) {
	    modify(individual, nextGeneration, modifiedGenes);
	}
    }
	    

    /**
     * Modify a gene to eliminate violations
     * 
     * @param individual
     * @param nextGeneration
     * @param gene
     */
    private static void modify(Individual individual,
	    Generation nextGeneration, Gene gene) {
	// exit if gene is already modified
	if (gene.isModified()) {
	    return;
	}

	Room[] rooms = RoomManager.getRooms();

	markTimeslotForRooms(individual.getGenes(), rooms);

	int newTimeslot;
	boolean[] isChosen = new boolean[Constant.TIMESLOT_NUM];
	Arrays.fill(isChosen, false);
	isChosen[gene.getTimeslot()] = true;
	
	for (short i = 0; i < Constant.MUTATION_NUM; i++) {
	    newTimeslot = getNextTimeslot(i);	    
	    if (!isChosen[newTimeslot]) {
		isChosen[newTimeslot] = true;
		for (short r = 0; r < rooms.length; r++) {
		    if (rooms[r].isFreeAtTimeslot(newTimeslot)) {
			nextGeneration.addIndividual(createNewIndividual(
				individual, gene, rooms[r].getRoomId(),
				newTimeslot));
			gene.setModified(true);
			break;
		    }
		}
	    }
	}
    }

    /**
     * @return
     */
    private static int getNextTimeslot(short i) {
	// TODO Auto-generated method stub
	int result = i;
	switch (mChooseTimeslotMode) {
	case LINEAR:
	    result = i;
	    break;
	case RANDOM:
	    result = Helper.getRadom(Constant.TIMESLOT_NUM);
	    break;
	default:
	    result = i;
	    break;
	}
	return result;
    }

    /**
     * Modify a set of genes to eliminate violations
     * 
     * @param individual
     * @param nextGeneration
     * @param modifiedGenes
     */
    private static void modify(Individual individual,
	    Generation nextGeneration, ArrayList<Gene> modifiedGenes) {
	for (Gene gene : modifiedGenes) {
	    modify(individual, nextGeneration, gene);
	}
    }

    /**
     * Modify genes to eliminate violation, for over-resource case
     * 
     * @param individual
     * @param nextGeneration
     * @param timeslot
     */
    private static void modify(Individual individual,
	    Generation nextGeneration, int timeslot) {
	ArrayList<Gene> genes = individual.getGenes();
	Room[] rooms = RoomManager.getRooms();

	markTimeslotForRooms(genes, rooms);

	for (Gene gene : genes) {
	    if (gene.getTimeslot() == timeslot) {
		modify(individual, nextGeneration, gene);
	    }
	}
    }

    /**
     * Create a new individual from modifying one gene
     * 
     * @param individual
     * @param gene
     * @param newRoom
     * @param newTimeslot
     * @return
     */
    private static Individual createNewIndividual(Individual individual,
	    Gene gene, short newRoomId, int newTimeslot) {
	Gene newGene = new Gene(gene);
	newGene.setRoomId(newRoomId);
	newGene.setTimeslot(newTimeslot);
	newGene.setModified(false);
	Individual newIndividual = new Individual(individual);
	newIndividual.updateGene(newGene);

	return newIndividual;
    }

    private static void markTimeslotForRooms(ArrayList<Gene> genes, Room[] rooms) {
	short roomId;

	for (short i = 0; i < rooms.length; i++)
	    for (short j = 0; j < Constant.TIMESLOT_NUM; j++) {
		rooms[i].freeTimeslot(j);
	    }

	for (Gene gene : genes) {
	    roomId = gene.getRoomId();
	    rooms[roomId].fillTimeslot(gene.getTimeslot());
	}
    }
}
